﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Color = System.Windows.Media.Color;
using Colors = System.Windows.Media.Colors;
using Ignitron.EEngine;
using Ignitron.EEngine.Maths;
using Ignitron.EEngine.Model;
using GameEngine;
using Board3D.Animation;
using System.Drawing;

namespace Board3D.Models
{
    public class MoveDoneEventArgs : EventArgs
    {
        public Move Move;
    }

    internal class BoardModel: Model
    {
        // board model
        private const string _sBoardModel = "sachovnice.X";

        // different styles of board
        private const string _sBoardTexture = "board8x8.jpg";
        private const string _sBoardTextureBW = "board8x8_blackandwhite.jpg";
        private const string _sBoardTextureMarble = "board8x8_marble.jpg";

        private const int mTextureSize = 1024;

        private string _sPicturePath = null;

        private EMatrix4 _matBoardRotation = EMatrix4.Identity;

        // piece models
        private ManModel _mdlMan = null;
        private KingModel _mdlKing = null;
        private EBoundingBox _bbBoard = null;

        // move animation params
        AnimationBlock _animationBlock = null;
        AnimationParams _animationParams = null;

        // field and figure highligting
        private Position _fieldMouseAt;
        private Position _figureToMove;
        private List<Position> lstFieldsToMove;

        // drag & drop operation
        private bool _bDragAndDrop = false;
        private EVector3 _vecDragFigurePos;
        private Position _posDraggedFigure;
        private Move _currentMove = null;

        public event EventHandler<MoveDoneEventArgs> MoveDoneEvent;

        public BoardModel()
        {
            _fieldMouseAt = Position.Empty;
            lstFieldsToMove = new List<Position>();
        }

        public GameManager GameManager
        { get; set; }


        /// <summary>
        /// Sets a new checkersboard style
        /// </summary>
        public void SetNewStyle(IEngine engine, enumBoardStyle style)
        {
            string sFile = Path.Combine(_sPicturePath, GetFileByStyle(style));
            base.LoadTexture(engine, sFile);           
        }

        /// <summary>
        /// The main method for loading stuff of models and its textures
        /// </summary>
        public void LoadModels(Engine engine, string sModelPath, string sPicturePath, enumBoardStyle style)
        {
            _sPicturePath = sPicturePath;

            // load board model
            string sFile = Path.Combine(sModelPath, _sBoardModel);
            base.Load(engine, sFile);                      

            sFile = Path.Combine(sPicturePath, GetFileByStyle(style));
            base.LoadTexture(engine, sFile);

            Mesh.FlipTextureX();

            SetTransformMatrix(GetPrimaryTransformMatrix());

            _bbBoard = Mesh.GetBoundingBox();

            // load piece models
            _mdlMan = new ManModel();
            _mdlMan.LoadModels(engine, sModelPath, sPicturePath);

            //// load king models
            _mdlKing = new KingModel();
            _mdlKing.LoadModels(engine, sModelPath, sPicturePath);
        }

        // perform initalization
        public void InitDynamicTexture(IEngine engine, int width, int height)
        {
            // enable dynamic texture rendering
            EnableDynamicTexture(engine, width, height);
        }

        /// <summary>
        /// Registers new move animation
        /// </summary>
        public void StartMoveAnimation(AnimationBlock block)
        {
            _animationBlock = block;
            _animationBlock.AnimationEvent += MoveAnimationEvent;
            _animationParams = new AnimationParams();
        }

        private string GetFileByStyle(enumBoardStyle style)
        {
            switch (style)
            {
                case enumBoardStyle.Classic:
                    return _sBoardTexture;
                case enumBoardStyle.BlackAndWhite:
                    return _sBoardTextureBW;
                case enumBoardStyle.Marble:
                    return _sBoardTextureMarble;
                default:
                    throw new ApplicationException("Unknown board style");
            }
        }

        /// <summary>
        /// Animation event handler, sets animation parametres
        /// </summary>
        private void MoveAnimationEvent(Position figurePos, EVector3 position, float alpha)
        {
            if (position == EVector3.Zero)
                _animationParams.SetAlphaColor(figurePos, alpha);
            else
                _animationParams.SetPosition(figurePos, position);
        }

        /// <summary>
        /// Finishes the move animation
        /// </summary>
        public void FinishMoveAnimation()
        {
            _animationBlock = null;
            _animationParams = null;
        }

        /// <summary>
        /// Main rendering routine
        /// </summary>
        public override void Render(IEngine engine, int w, int h)
        {
            // render checkers board
            base.Render(engine, w, h);

            // render figures
            RenderFigures(engine, w, h);
        }

        /// <summary>
        /// Renders figures
        /// </summary>
        private void RenderFigures(IEngine engine, int w, int h)
        {
            if (GameManager == null)
                return;

            // calculate matrices
            engine.PushMatrix();
            engine.MultMatrix(ref _matBoardRotation);

            for (int row = 0; row < GameManager.GameBoard.Size; row++)
            {
                for (int col = 0; col < GameManager.GameBoard.Size; col++)
                {
                    enumFigureType figureType = GameManager.GameBoard.GetField(row, col);
                    if(figureType != enumFigureType.Nothing)
                    {
                        bool bSelected = false;

                        // check if to highlight figure to move
                        if ( (_figureToMove.Row == row && _figureToMove.Col == col) ||
                            (_bDragAndDrop == true && row == _posDraggedFigure.Row && col == _posDraggedFigure.Col))
                            bSelected = true;

                        // get figure translation
                        EVector3 tranlateVector;
                        float alpha;
                        GetFigurePositionAndBlend(row, col, out tranlateVector, out alpha);

                        // render figure
                        engine.PushMatrix();
                        EMatrix4 translateMatrix = EMatrix4.CreateTranslation(tranlateVector);
                        engine.MultMatrix(ref translateMatrix);

                        _mdlKing.EnableBlending(alpha);
                        _mdlMan.EnableBlending(alpha);

                        
                        if (figureType.FigureColor() == enumFigureColor.White)
                        {
                            if(figureType.IsKing())
                                _mdlKing.Render(engine, GameEngine.enumFigureColor.White, bSelected, w, h);
                            else
                                _mdlMan.Render(engine, GameEngine.enumFigureColor.White, bSelected, w, h);
                        }
                        else
                        {
                            if (figureType.IsKing())
                                _mdlKing.Render(engine, GameEngine.enumFigureColor.Black, bSelected, w, h);
                            else
                                _mdlMan.Render(engine, GameEngine.enumFigureColor.Black, bSelected, w, h);
                        }

                        _mdlMan.DisableBlending();
                        _mdlKing.DisableBlending();

                        engine.PopMatrix();
                    }
                }
            }

            engine.PopMatrix();
        }

        public override EMatrix4 GetPrimaryTransformMatrix()
        {
            return  EMatrix4.CreateRotationZ((float)Math.PI) * EMatrix4.CreateRotationX((float)(Math.PI / 2));
            //return EMatrix4.CreateRotationX((float)(Math.PI / 2)) * EMatrix4.CreateRotationZ((float)Math.PI);
        }

        /// <summary>
        /// Sets board rotation
        /// </summary>
        public void SetBoardRotation(float angleX, float angleY)
        {
            EMatrix4 rotationX = EMatrix4.CreateRotationX(angleX);
            EVector3 vecRotation = EVector3.TransformVector(EVector3.UnitZ, rotationX);
            //EVector3 vecRotation = EVector3.TransformVector(EVector3.UnitX, rotationX);
            //_matBoardRotation = EMatrix4.CreateFromAxisAngle(vecRotation, angleY) * rotationX;
            _matBoardRotation = rotationX * EMatrix4.CreateFromAxisAngle(vecRotation, angleY);

            SetTransformMatrix(GetPrimaryTransformMatrix() * _matBoardRotation);
        }

        /// <summary>
        /// Returns the position of a figure and its blending parameter
        /// </summary>
        private void GetFigurePositionAndBlend(int row, int col, out EVector3 vec, out float alpha)
        {
            alpha = 1;

            // first try to probe animation
            if (_animationParams != null)
            {
                alpha = _animationParams.GetAlphaColor(new Position(row, col));

                if (_animationParams.GetPositionVector(new Position(row, col), out vec) == true)
                {
                    return;
                }
            }

            // return drag & drop position if in progress
            if (_bDragAndDrop == true)
            {
                if (_posDraggedFigure.Row == row && _posDraggedFigure.Col == col)
                {
                    vec = _vecDragFigurePos;
                    return;
                }
            }

            // if not animation, calculate regular piece position on checkers board
            float x, y, z;
            GetFigurePosition(row, col, out x, out y, out z);
            vec = new EVector3(x, y, z);
        }

        /// <summary>
        /// Returns figure position in 3D-space, public because of the required access by animation builder
        /// </summary>
        public void GetFigurePosition(int row, int col, out float X, out float Y, out float Z)
        {
            X = 0;
            Y = 0;
            Z = -0.03f;
            float paddingRow = 0.130f;
            float paddingCol = 0.132f;
            float Xmove = 0.130f;
            float Ymove = 0.130f;

            _bbBoard = Mesh.GetBoundingBox();

            row = 7 - row;

            float sizeX = _bbBoard.WidthX;
            float sizeY = _bbBoard.WidthZ; // model of board is 90 degress rotated

            sizeX -= 2 * paddingCol;
            sizeY -= 2 * paddingRow;

            float fieldSizeX = sizeX / 8f;
            float fieldSizeY = sizeY / 8f;

            X = col * fieldSizeX + fieldSizeX / 2f;
            Y = row * fieldSizeY + fieldSizeY / 2f;

            X += Xmove + _bbBoard.MinX;
            Y += Ymove + _bbBoard.MinZ;
        }

        protected override void DrawDynamicTexture(IEngine engine, int w, int h)
        {
            // copy the model texture
            engine.DrawTexture(mTexture, 0, 0, w, h);          

            // highlight field the mouse is at
            if (_fieldMouseAt != Position.Empty)
            {
                Rectangle rect = GetRectangleForPosition(_fieldMouseAt.Row, _fieldMouseAt.Col);
                engine.DrawRect(rect.X, rect.Y, rect.Width, rect.Height, Color.FromArgb(255, 192, 0, 0).ToEE());
            }

            // highlight fields to move
            foreach (Position field in lstFieldsToMove)
            {
                Rectangle rect = GetRectangleForPosition(field.Row, field.Col);
                int marginX = rect.Width / 4;
                int marginY = rect.Height / 4;
                Rectangle newRect = new Rectangle(rect.X + marginX, rect.Y + marginY, marginX * 2, marginY * 2);

                engine.DrawRect(newRect.X, newRect.Y, newRect.Width, newRect.Height, Colors.AntiqueWhite.ToEE());
            }
        }

        #region Helper Computing Methods

        protected void SetDraggedFigurePosition(EVector3 collisionVector)
        {
            Model pieceModel = GameManager.GameBoard.GetField(_posDraggedFigure.Row, _posDraggedFigure.Col).IsKing() 
                    == false ? (Model)_mdlMan : (Model)_mdlKing;

            EBoundingBox bb = pieceModel.Mesh.GetBoundingBox();
            EVector3 max = bb.Max;
            EVector3 min = bb.Min;
            max = EVector3.Transform(max, pieceModel.GetPrimaryTransformMatrix());
            min = EVector3.Transform(min, pieceModel.GetPrimaryTransformMatrix());
            double sizeX = max.X - min.X;
            double sizeY = max.Y - min.Y;

            collisionVector = EVector3.Transform(collisionVector, GetPrimaryTransformMatrix());
            // collisionVector.X -= sizeX / 2;
            //collisionVector.Y -= sizeY / 2;
            collisionVector.Z += 0.1f;

            _vecDragFigurePos = collisionVector;

        }

        protected bool FindFieldByCoordinates(Point mousePoint, out int row, out int col)
        {
            row = -1;
            col = -1;

            for (int r = 0; r < 8; r++)
                for (int c = 0; c < 8; c++)
                {
                    Rectangle rect = GetRectangleForPosition(r, c);
                    if (rect.Contains(mousePoint) == true)
                    {
                        row = r;
                        col = c;
                        return true;
                    }
                } // for
            return false;
        }

        protected Rectangle GetRectangleForPosition(int row, int col)
        {
            const int marginX = 70;
            const int marginY = 70;
            float fieldSizeX = (mTextureSize - marginX * 2) / 8;
            float fieldSizeY = (mTextureSize - marginY * 2) / 8;
            Rectangle rect = new Rectangle((int)(col * fieldSizeX + marginX), (int)((7 - row) * fieldSizeY + marginY), (int)Math.Round(fieldSizeX, 0), (int)Math.Round(fieldSizeY));
            return rect;
        }

        #endregion

        #region Mouse Handlers

        public void UpdateDragAndDropStart(Point mousePoint, EVector3 collisionVector)
        {
            // check field to start dragging
            int row, col;
            if (FindFieldByCoordinates(mousePoint, out row, out col) == true)
            {
                enumFigureType figureType = GameManager.GameBoard.GetField(row, col);
                if (figureType != enumFigureType.Nothing && figureType.FigureColor() == GameManager.GameBoard.OnMove)
                {
                    List<Position> lstDummy = new List<Position>();
                    GameManager.GetFigureMoves(new Position(row, col), lstDummy);
                    if (lstDummy.Count > 0)
                    {
                        _bDragAndDrop = true;
                        _posDraggedFigure = new Position(row, col);
                        SetDraggedFigurePosition(collisionVector);
                    }
                }
            }
        }

        public void UpdateDragAndDropFinish(Point mousePoint)
        {
            _bDragAndDrop = false;

            // evaluate start and stop position
            Position startPos = _posDraggedFigure;
            int row, col;
            FindFieldByCoordinates(mousePoint, out row, out col);          
            Position endPos = new Position(row, col);
            if (startPos != Position.Empty && endPos != Position.Empty)
            {
                if (GameManager.CanFigureMoveHere(startPos, endPos) == true)
                {
                    Move move = GameManager.CompleteMoveStep(startPos, endPos);
                    GameManager.DoMoveStep(move);
                    AddMove(move);
                }
            }

            _posDraggedFigure = Position.Empty;

            return;

        }

        /// <summary>
        /// Adds single move step, calls event when the move is finished
        /// </summary>
        protected void AddMove(Move move)
        {
            if (_currentMove == null)
            {
                _currentMove = move;
            }
            else
            {
                Move lastMove = _currentMove;
                while (lastMove.nextMove != null)
                    lastMove = lastMove.nextMove;
                lastMove.nextMove = move;
            }

            if (GameManager.DoesMoveContinue(move, move.endPos) == false)
            {
                CallMoveDoneEvent();
            }
        }

        /// <summary>
        /// Fires the event, when the move is finished
        /// </summary>
        protected void CallMoveDoneEvent()
        {
            MoveDoneEventArgs e = new MoveDoneEventArgs();
            e.Move = _currentMove;
            _currentMove = null;

            if (MoveDoneEvent != null)
            {
                MoveDoneEvent(this, e);
            }
        }
           
        
        /// <summary>
        /// Updates the mouse motion over the board
        /// </summary>
        public void UpdateMouseHover(Point mousePoint, EVector3 collisionVector)
        {
            _figureToMove = Position.Empty;
            lstFieldsToMove.Clear();

            // check field to highlight
            int row, col;
            if (FindFieldByCoordinates(mousePoint, out row, out col) == true)
            {
                // check figure to move to highlight
                _fieldMouseAt = new Position(row, col);
                enumFigureType figureType = GameManager.GameBoard.GetField(row, col);
                if (figureType != enumFigureType.Nothing && figureType.FigureColor() == GameManager.GameBoard.OnMove)
                {
                    GameManager.GetFigureMoves(_fieldMouseAt, lstFieldsToMove);
                    if (lstFieldsToMove.Count > 0)
                    {
                        _figureToMove = _fieldMouseAt;
                    }
                }
            }
            else
            {
                _fieldMouseAt = Position.Empty;
            }

            // update drag & drop
            if (_bDragAndDrop == true)
            {
                SetDraggedFigurePosition(collisionVector);                
                lstFieldsToMove.Clear();
            }
        }
        #endregion
    }
}
